package com.zoe.onelink.actuate.feign;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import feign.Target;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-03-01
 */
@RequiredArgsConstructor
public class FeignInfoContributor implements InfoContributor {

    private final ApplicationContext applicationContext;

    @Override
    public void contribute(Info.Builder builder) {
        try {
            Table<String, String, FeignClientInfo> table = this.parseFeignClientInfo();
            builder.withDetail("feignClients", table.rowMap());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    private Table<String, String, FeignClientInfo> parseFeignClientInfo() {
        Table<String, String, FeignClientInfo> clientTable = HashBasedTable.create();
        Map<String, Object> clientMap = this.applicationContext.getBeansWithAnnotation(FeignClient.class);
        for (Object value : clientMap.values()) {
            Object h = ReflectUtil.getFieldValue(value, "h");
            Target.HardCodedTarget target = (Target.HardCodedTarget) ReflectUtil.getFieldValue(h, "target");
            if (target == null) {
                continue;
            }
            Class<?> clientClass = target.type();
            Method[] methods = ReflectUtil.getMethods(clientClass);
            if (methods == null || methods.length == 0) {
                continue;
            }
            FeignClient feignClient = clientClass.getDeclaredAnnotation(FeignClient.class);
            String basePath = StrUtil.nullToEmpty(feignClient.path());
            String serviceName = feignClient.value();
            for (Method method : methods) {
                Annotation anno = Arrays.stream(method.getDeclaredAnnotations())
                        .filter(annotation -> annotation.annotationType().isAnnotationPresent(RequestMapping.class))
                        .findFirst()
                        .orElse(null);
                RequestMethod[] reqMethods = Objects.requireNonNull(AnnotationUtils.findAnnotation(method, RequestMapping.class)).method();
                RequestMethod reqMethod = reqMethods.length == 0 ? null : reqMethods[0];
                String reqPath = anno == null ? "" : ArrayUtil.get(AnnotationUtils.getValue(anno), 0);
                String reqUri = basePath + reqPath;
                List<FeignClientInfo.Arg> args = Arrays.stream(method.getParameters())
                        .map(parameter -> {
                            List<String> annotations = Arrays.stream(parameter.getAnnotations()).map(annotation -> annotation.annotationType().getSimpleName()).collect(Collectors.toList());
                            return new FeignClientInfo.Arg()
                                    .setName(parameter.getName())
                                    .setType(parameter.getType().getSimpleName())
                                    .setAnnotations(annotations);
                        }).collect(Collectors.toList());
                String returnFullType = method.getGenericReturnType().toString().replace("class ", "");
                String returnTypeSimpleStr = returnFullType.replaceAll("[a-zA-Z]+\\.", "");

                List<String> exceptions = Arrays.stream(method.getGenericExceptionTypes()).map(Type::getTypeName).collect(Collectors.toList());
                List<String> annotations = Arrays.stream(method.getAnnotations()).map(annotation -> annotation.annotationType().getSimpleName()).collect(Collectors.toList());

                String reqMethodName = reqMethod == null ? "ALL" : reqMethod.name();
                FeignClientInfo feignClientInfo = new FeignClientInfo()
                        .setRequestMethod(reqMethodName)
                        .setMethodName(method.getName())
                        .setArgs(args)
                        .setUri(reqUri)
                        .setReturnType(returnTypeSimpleStr)
                        .setExceptions(exceptions)
                        .setAnnotations(annotations)
                        .setClientClass(clientClass.getName());
                clientTable.put(serviceName, reqMethodName + " " + reqUri, feignClientInfo);
            }
        }
        return clientTable;
    }
}

